When writing code during the development of software applications, developers commonly spend a significant amount of time “debugging” the code to find runtime errors in the code. In doing so, developers may take several approaches to reproduce and localize a source code bug, such as observing behavior of a program based on different inputs, inserting debugging code (e.g., to print variable values, to track branches of execution, etc.), temporarily removing code portions, etc. Tracking down runtime errors to pinpoint code bugs can occupy a significant portion of application development time.
Many types of debugging applications (“debuggers”) have been developed in order to assist developers with the code debugging process. These tools offer developers the ability to trace, visualize, and alter the execution of computer code. For example, debuggers may visualize the execution of code instructions, may present variable values at various times during code execution, may enable developers to alter code execution paths, and/or may enable developers to set “breakpoints” and/or “watchpoints” on code elements of interest (which, when reached during execution, causes execution of the code to be suspended), among other things.
An emerging form of debugging applications enable “time travel,” “reverse,” or “historic” debugging, in which execution of a program is recorded/traced by a trace application into one or more trace files, which can be then be used to replay execution of the program later for forward and backward analysis. For example, “time travel” debuggers can enable a developer to set forward breakpoints/watchpoints (like conventional debuggers) as well as reverse breakpoints/watchpoints.
One challenge in implementing “time travel” debuggers involves how to detect when a point of interest (e.g., breakpoint/watchpoint) has been encountered during replay of a trace file in a flexible and performant manner. One mechanism that has been explored for tracking points of interest during replay is to use processor-implemented hardware breakpoints. While performant, use of hardware breakpoints has the disadvantages of severely limiting the number of breakpoints that can be concurrently watched for (e.g., four in INTEL processors), and inherently binds a debugger implementation to specifically supported hardware.
Another mechanism that has been explored is to use software breakpoints, in which each traced operation that accesses memory (e.g., a read or a write to memory storing data, or a read/fetch for execution from memory storing program code) is instrumented with additional code instructions that check whether or not the access would cause a breakpoint to be encountered. However, instrumentation has the severe disadvantage of causing a check for the occurrence of a breakpoint on every memory access, regardless of whether or not that memory access would actually encounter a breakpoint. In many environments, execution of the instrumented code instructions results in the use of tens to hundreds of additional processor cycles for each memory access, which has a significant detrimental effect on replay performance that is perceivable by a human user.